package cn.miju.unionpay.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

/**
 * <pre>
 * 银联接口签名工具类
 * </pre>
 *
 * @author zhuming
 * @version 1.0.0
 * @since 2023-03-12 18:12
 */
@Slf4j
public class UnionPaySignUtil {

    /**
     * 签名
     * @param reqData 签名数据
     * @param certPath 签名证书路径
     * @param certPwd 签名证书密码
     * @return 签名摘要值base64
     */
    public static Map<String, String> sign(Map<String, String> reqData, String certPath, String certPwd) {
        try{
            UnionPayCertUtil.Cert cert = UnionPayCertUtil.getCert(certPath, certPwd);
            reqData.put("certId", cert.getCertId());
            String stringData = createLinkString(reqData, true, false, StandardCharsets.UTF_8.toString());
            log.info("排序串：{}", stringData);
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(stringData.getBytes(StandardCharsets.UTF_8));
            String sha256Hex = new String(Hex.encodeHex(messageDigest.digest(), true)).toLowerCase();
            log.info("sha256结果：{}", sha256Hex);
            Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, cert.getPrivateKey().getEncoded(), null);
            String signBase64 = Base64.encode(sign.sign(sha256Hex));
            log.info("摘要：{}", signBase64);
            reqData.put("signature", signBase64);
            return reqData;
        } catch (Exception e) {
            log.error("签名失败！", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 把请求要素按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param para
     *            请求要素
     * @param sort
     *            是否需要根据key值作升序排列
     * @param encode
     *            是否需要URL编码
     * @return 拼接成的字符串
     */
    public static String createLinkString(Map<String, String> para, boolean sort, boolean encode, String charset) {
        List<String> keys = new ArrayList<String>(para.keySet());
        if (sort) {
            Collections.sort(keys);
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = para.get(key);
            if (encode && value != null) {
                try {
                    value = URLEncoder.encode(value, charset);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(charset + "送错了.");
                }
            }
            if (i == keys.size() - 1) {
                sb.append(key).append("=").append(value);
            } else {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        return sb.toString();
    }

    /**
     * 解析应答字符串，生成应答要素。
     * 处理全渠道应答报文那种不带url编码又可能在value里成对出现括号且括号里带&和=的情况。
     *
     * @param str 需要解析的字符串
     * @return 解析的结果map
     */
    public static Map<String, String> readLinkStr(String str) {
        Map<String, String> map = new HashMap<>();
        int len = str.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
            curChar = str.charAt(i);// 取当前字符
            if (isKey) {// 如果当前生成的是key

                if (curChar == '=') {// 如果读取到=分隔符
                    key = temp.toString();
                    temp.setLength(0);
                    isKey = false;
                } else {
                    temp.append(curChar);
                }
            } else  {// 如果当前生成的是value
                if(isOpen){
                    if(curChar == openName){
                        isOpen = false;
                    }
                }else{//如果没开启嵌套
                    if(curChar == '{'){//如果碰到，就开启嵌套
                        isOpen = true;
                        openName ='}';
                    }
                    if(curChar == '['){
                        isOpen = true;
                        openName =']';
                    }
                }
                if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                    putKeyValueToMap(temp, isKey, key, map, false, null);
                    temp.setLength(0);
                    isKey = true;
                } else {
                    temp.append(curChar);
                }
            }
        }
        putKeyValueToMap(temp, isKey, key, map, false, null);
        return map;
    }
    private static void putKeyValueToMap(StringBuilder temp, boolean isKey, String key, Map<String, String> map,
                                         boolean decode, String charset) {
        try {
            if (decode) {
                key = URLDecoder.decode(key, charset);
            }
            if (isKey) {
                key = temp.toString();
                map.put(key, "");
            } else {
                if (decode) {
                    String value = URLDecoder.decode(temp.toString(), charset);
                    map.put(key, value);
                } else {
                    map.put(key, temp.toString());
                }
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("编码有问题: " + charset);
        }
    }

}
